Add ServerError class and exponential backoff retry for 5xx errors#262
Conversation
Fixes GUMROAD-MOBILE-22 When the Gumroad backend is slow or overloaded, Cloudflare returns a 502 Bad Gateway with an HTML error page. Previously, request.ts threw a generic Error with the full HTML body embedded in the message. This made errors hard to distinguish and cluttered logs. Changes: - Add ServerError subclass of Error in request.ts that captures the HTTP status code, so callers can distinguish transient server errors from application errors - Throw ServerError for all 5xx responses with a clean message (no HTML body leaking into the error message) - Add retryDelay with exponential backoff (1s, 2s, 4s, ... up to 30s) in useAPIRequest for ServerError, so retries don't hammer an already overloaded backend - Update existing tests and add new tests for ServerError type checking and backoff timing
Greptile SummaryThis PR introduces a typed
Confidence Score: 5/5Safe to merge — changes are narrowly scoped to error classification and retry timing, with no effect on the happy path. The 5xx guard fires before the body is read and only adds a new typed throw; the existing 401 and generic error paths are untouched. The retryDelay function is additive and the previous thread's regressions were both addressed in the follow-up commit with explicit test coverage for each case. No files require special attention. Important Files Changed
Reviews (3): Last reviewed commit: "Format retryDelay tests" | Re-trigger Greptile |
|
Assigning @nyomanjyotisa per Gianfranco. |
What
When the Gumroad backend is slow or overloaded, Cloudflare returns a 502 Bad Gateway with an HTML error page instead of valid JSON. Previously,
request.tsthrew a genericErrorwith up to 10,000 characters of the Cloudflare HTML body embedded in the message, making errors hard to distinguish programmatically and cluttering logs/Sentry.This PR adds:
ServerErrorsubclass ofErrorinrequest.tsthat captures the HTTP status code, so callers can useinstanceof ServerErrorto distinguish transient server errors from application-level errorsServerErrorwith a clean message (Request failed: 502) — no HTML body leaks into the error messageretryDelayinuseAPIRequestforServerErrorretries (1s → 2s → 4s → ... capped at 30s), so retries don't hammer an already-overloaded backendWhy
The root cause is Cloudflare returning 502 when the upstream Gumroad backend fails to respond within its timeout. The mobile app was treating these like any other error — retrying immediately and including the full HTML error page in error messages.
Exponential backoff gives the backend time to recover between retries. The typed
ServerErrorlets the retry infrastructure distinguish transient server failures from client errors (4xx), and keeps error messages clean for logging and Sentry.Test results
All 198 tests pass across 28 test suites, including new tests for:
ServerErrorthrown on 5xx with correctstatusCodeServerErrorretriesBuilt with Claude Code (claude-sonnet-4-20250514). Prompted to fix GUMROAD-MOBILE-22 (502 Bad Gateway handling).
Need help on this PR? Tag
/codesmithwith what you need. Autofix is disabled.Note
Medium Risk
Touches shared HTTP and React Query retry behavior for all API calls; behavior change is intentional (cleaner errors, slower 5xx retries) but affects every consumer of useAPIRequest.
Overview
5xx handling in
lib/request.tsnow throws a typedServerError(withstatusCode) for HTTP ≥500, using a short message likeRequest failed: 502instead of reading the response body—so Cloudflare HTML no longer ends up in errors or Sentry.Retries on
useAPIRequestgain aretryDelaywith exponential backoff (1s base, 30s cap) forServerErrorand as the default for other retriable failures; callers can still pass numeric or functionretryDelayfor non-server errors.Tests cover clean 5xx/502 messages, hook-level
ServerError, backoff timing, and existing 5xx retry cases updated for fake timers.Reviewed by Cursor Bugbot for commit dadd27f. Bugbot is set up for automated code reviews on this repo. Configure here.